package checker

import (
	"kumachan/interpreter/compiler/checker/checked"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/checker/typsys"
	"kumachan/interpreter/lang/textual/ast"
)


type checkResult struct {
	expr  *checked.Expr
	err   *source.Error
}
func (checkResult) Error() string {
	// abuse IDE inspection to ensure result returned
	panic("dummy method")
}

func (cc *checkContext) error(content source.ErrorContent) checkResult {
	return cc.propagate(source.MakeError(cc.location, content))
}
func (cc *checkContext) throw(err *source.Error) checkResult {
	if err == nil {
		panic("something went wrong")
	}
	return checkResult { err: err }
}
func (cc *checkContext) propagate(err *source.Error) checkResult {
	if err == nil {
		panic("something went wrong")
	}
	return checkResult { err: err.IncreaseDepth() }
}
func (cc *checkContext) confidentlyTrust(t typsys.Type, content checked.ExprContent) checkResult {
	return checkResult { expr: &checked.Expr {
		Type:    t,
		Info:    checked.ExprInfoFrom(cc.location),
		Content: content,
	} }
}
func (cc *checkContext) confidentlyTrustExpr(expr *checked.Expr) checkResult {
	return checkResult { expr: expr }
}

func (cc *checkContext) forward(checker ExprChecker) checkResult {
	var expr, s, err = checker(cc.expected, cc.inferring, cc.exprContext)
	if err != nil { return checkResult { err: err } }
	cc.inferring = s
	return checkResult { expr: expr }
}
func (cc *checkContext) forwardToChildExpr(node ast.Expr) checkResult {
	var expr, err = cc.checkChildExpr(cc.expected, node)
	return checkResult {
		expr: expr,
		err:  err,
	}
}
func (cc *checkContext) forwardToChildTerm(node ast.VariousTerm) checkResult {
	return cc.forwardToChildExpr(ast.WrapTermAsExpr(node))
}

func (cc *checkContext) assign(t typsys.Type, content checked.ExprContent) checkResult {
	if cc.expected == nil {
		var t, ok = cc.getCertainType(t)
		if !(ok) { panic("something went wrong") }
		var info = checked.ExprInfoFrom(cc.location)
		var expr = &checked.Expr {
			Type:    t,
			Info:    info,
			Content: content,
		}
		return checkResult { expr: expr }
	} else {
		var info = checked.ExprInfoFrom(cc.location)
		var s0 = cc.inferring
		var assign_ctx = cc.exprContext.makeAssignContext(s0)
		var ok, s1 = typsys.Assign(cc.expected, t, assign_ctx)
		if ok {
			cc.inferring = s1
			var expr = &checked.Expr {
				Type:    t,
				Info:    info,
				Content: content,
			}
			return checkResult { expr: expr }
		} else {
			var err = source.MakeError(info.Location,
				E_NotAssignable {
					From: typsys.DescribeType(t, s0),
					To:   typsys.DescribeType(cc.expected, s0),
				})
			return checkResult { err: err }
		}
	}
}
func (cc *checkContext) assignFinalNode(node ast.Expr, f func(*checked.Expr)(checked.ExprContent)) checkResult {
	var expr, err = cc.checkChildExpr(cc.expected, node)
	if err != nil { return checkResult { err: err } }
	return checkResult {
		expr: &checked.Expr {
			Type:    expr.Type,
			Info:    expr.Info,
			Content: f(expr),
		},
	}
}
func (cc *checkContext) assignExpr(expr *checked.Expr) checkResult {
	return cc.assign(expr.Type, expr.Content)
}

func (cc *checkContext) infer (
	pd  [] typsys.Parameter,
	pv  [] typsys.Type,
	t   typsys.Type,
	k   func(*typsys.InferringState) (
		checked.ExprContent,
		*typsys.InferringState,
		*source.Error,
	),
) checkResult {
	if len(pv) > len(pd) {
		return cc.error(
			E_TypeParametersExceededArity {
				Arity: uint(len(pd)),
			})
	}
	var mod = cc.exprContext.ModName
	var s0 = typsys.StartInferring(pd)
	for i, v := range pv {
		var p = &(pd[i])
		var pt = typsys.ParameterType { Parameter: p }
		var a = typsys.MakeAssignContext(mod, s0)
		var ok, s0_ = typsys.Assign(pt, v, a)
		if !(ok) {
			return cc.error(
				E_InvalidTypeParameter {
					Index: uint(i),
					Name:  p.Name,
				})
		}
		s0 = s0_
	}
	var required, available = cc.getExpectedCertainOrInferred(cc.expected)
	if available {
		var a0 = typsys.MakeAssignContext(mod, s0)
		var ok, s1 = typsys.Assign(required, t, a0)
		if !(ok) {
			return cc.error(
				E_NotAssignable {
					From: typsys.DescribeType(t, s0),
					To:   typsys.DescribeType(required, nil),
				})
		}
		var content, _, err = k(s1)
		if err != nil { return cc.propagate(err) }
		var expr = &checked.Expr {
			Type:    required,
			Info:    checked.ExprInfoFrom(cc.location),
			Content: content,
		}
		return cc.confidentlyTrustExpr(expr)
	} else {
		var content, s1, err = k(s0)
		if err != nil { return cc.propagate(err) }
		for i := range pd {
			var p = &(pd[i])
			var _, ok = s1.GetCurrentValue(p)
			if !(ok) {
				var err = source.MakeError(cc.location,
					E_TypeParameterNotInferred {
						Index: uint(i),
						Name:  p.Name,
					})
				return cc.propagate(err)
			}
		}
		var t_inferred, ok = typsys.TypeOpGetInferredType(t, s1)
		if !(ok) { panic("something went wrong") }
		return cc.assign(t_inferred, content)
	}
}


